Function Assert-AdminRight(){#check if the current powershell process is running as administrator
	return ([Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
Function Pop-Message(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$info,
        [Parameter(Mandatory=$False)]
        [string]$window_title='Script Running Error',
        [string]$info_type='Error'
    )
    [void][System.Windows.Forms.MessageBox]::Show($info, $window_title, 'OK', $info_type)
}
Function New-IOItem($p,[Switch]$d){
  if(!(test-path $p -ea 0)){if($d){new-item -ItemType Directory -path $p -Force -ea 0|out-null}else{new-item -ItemType File -path $p -Force -ea 0|out-null}}
}
Function Write-Log(){
    [CmdletBinding()]param(
		[Parameter(Mandatory=$true)]
		[ValidateSet("Warning", "Error", "Success","Normal","Critical")]
		[string]$log_type,
		[string]$log_info,
		[string]$log_file="$home\Documents\WindowsPowerShell\log.txt",
        [Parameter(Mandatory=$false)]
        [switch]$print_to_console
	)
    If( $print_to_console ){
        $color = switch -regex ($log_type){
            "Error|Critical"  {'red';break }
            "Warning"{'yellow' ;break}
            "Success"{'green'  ;break}
            default {'white'}
        }
        write-host -fore $color $log_info
    }
	Do{
	    Try{"{0:yyyy-MM-dd HH:mm:ss}--{1}: {2}" -f (get-date),$log_type,$log_info |Out-File -FilePath $log_file -Append -Encoding utf8 -Force -ea 1;break}Catch{sleep -Milliseconds 200 ;if(++$cnt -gt 10){write-error "wait too long(2s) to write log";break}
		}Finally{}
	}While(1)
}
Function Get-XMLValue(){
    [CmdletBinding()]param(
		[Parameter(Mandatory=$True)]
		[object]$xml_object,		
		[ValidateNotNullOrEmpty()][string]$xpath,
        [Parameter(Mandatory=$false)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
    Try{
        return ($xml_object.SelectSingleNode($xpath.ToLower()).InnerText)
    }Catch{
        write-log "Warning" "xpath is case-sensitive" $log_file
        return $null
    }Finally{}
}
Function Encrypt-String([string]$secpwd){
    return ($secpwd | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString)
}
Function Decrypt-String([string]$str){#$secpwd="p@ssW0rd1" | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
    return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((ConvertTo-SecureString $str )))
}
Function Connect-FTP(){
    [CmdletBinding()]
	param(
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Username,
		[Parameter(Mandatory=$false)]
		[string]$Password,
		[Parameter(Mandatory=$false)]
		[string]$ftpServer='isgswapd1n1',
		[Parameter(Mandatory=$true)]
		[string]$remote_file,
		[Parameter(Mandatory=$true)]
		[string]$local_file,
		[Parameter(Mandatory=$false)]
		[ValidateSet("Download", "Upload")]
		[string]$ftp_action="Download",
		[Parameter(Mandatory=$false)]
		[string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
    $remote_path ="$ftpServer/$remote_file"
    $ftp_server_uri="ftp://"+$Username+":"+$Password+"@"+$ftpServer
	$webclient = New-Object System.Net.WebClient
	$remote_file_uri = New-Object System.Uri("$ftp_server_uri/$remote_file")
    Switch ($ftp_action){
        "Download"{
            Try{
                $webclient.DownloadFile($remote_file_uri, $local_file)
                Write-Log Success "Have downloaded file $local_file from $remote_path" $log_file ; return $true
            }Catch{
                Write-Log Error $_.exception $log_file ; return $false
            }Finally{}
            }
        "Upload"{
            Try{
                If( test-path $local_file){
                    $webclient.UploadFile($remote_file_uri, $local_file); 
                    Write-Log Success "Have uploaded file $local_file to $remote_path" $log_file; return $true
                }else{
                    Write-Log Error "File localFile doesn't exist at all, please double check" $log_file ; return $false
                }
            }Catch{
                Write-Log Error $_.exception $log_file ; return $false
            }Finally{}
        }
    }
}
Function Get-CmdkeyList(){
    $ckList = cmdkey /list|?{$_ -imatch 'Target|User'}; $ckPair=@()
	for($n=0;$n -le $ckList.count;$n=$n+2){
		try{
			$ckPair+=$ckList[$n].trim().split("=")[-1] + "-"+$ckList[$n + 1].split(":")[-1].trim()
		}catch{}
	}
	return $ckPair
}
Function Connect-RemoteComputer(){
    [CmdletBinding()]
	param(
		[Parameter(Mandatory=$true)]
		[Alias("cn")]$ComputerName,
		[Parameter(Mandatory=$false)]
        [ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
		$user = "nam\aaadmin",
		[Parameter(Mandatory=$false)]
		$passwd=$aaadminPWD,
		[Switch]$resize
	)
	if($user -inotmatch "^aaadmin(_p)?"){
		try{
            while(($passwd -eq $null) -or ($passwd.trim() -eq "")){
                $passwd = Get-Credential -UserName $user -Message "Please enter password again as it was empty!"
                $passwd = $passwd.GetNetworkCredential().Password
            }
		}catch{}finally{}
	}else{
		if($user -eq "nam\aaadmin_p"){$passwd =$aaadmin_pPWD}
	}
    cmdkey.exe /generic:$ComputerName /user:$user /pass:$passwd
	If( !$resize ){mstsc.exe /v $ComputerName /w 1804 /h 1015}else{mstsc.exe /v $ComputerName /w 1376 /h 768}
}
Function Connect-VMServer(){
    [CmdletBinding()]
	param(
		[Parameter(Mandatory=$True)]
		[string]$detail,
		[Parameter(Mandatory=$false)]
		[Switch]$w,
		[Switch]$d
	)
	$domain=Switch($detail[0]){
	    "N"{"NAM";break}
	    "A"{"APAC";break}
	    "E"{"EMEA"}
	}
	$env=Switch($detail[1]){
	    "P"{"PROD";break}
	    "U"{"UAT";break}
	    "D"{"DEV";break}
	    "O"{"POC";break}
	    "T"{"PTE";break}
	    "L"{"LAB"}
	}
    $ComputerName=(Get-XMLValue $powershell_config "//$domain/$env/$node").split(',')[0]
    $user,$passwd=Switch -regex ($env){
        "PROD|COB|PTE"{"nam\aaadmin_p",$aaadmin_pPWD;break}
        default{"nam\aaadmin",$aaadminPWD}
    }
    Try{cmdkey.exe /generic:$ComputerName /user:$user /pass:$passwd 1>$null}Catch{}Finally{}
	mstsc.exe /v $ComputerName /w 1804 /h 1015
}
Function Get-WindowsTitle(){
    $word = new-object -com 'word.application'
    $rst=$word.tasks | ? {$_.visible} | select name
    $word.quit()
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word)
    Remove-Variable word -ea 0
    return $rst
}
Function Get-OpenedRDC([Switch]$windowTiltle){
	If( ($title=(Get-Process mstsc -ea 0).MainWindowTitle) -ne $null ){
	    if($windowTiltle){$title|%{($_ -split("- Remote"))[0].trim()}}else{$title}
	}
}
Function Remove-Cmdkey(){
    [CmdletBinding()]param(
		[Parameter(Mandatory=$True,ParameterSetName="config",Position=0)]
		[string]$domain,
		[Parameter(Mandatory=$True,ParameterSetName="config",Position=1)]
		[string]$env,
		[Parameter(Mandatory=$False,ParameterSetName="config",Position=2)]
		[string]$node="CR1",
		[Parameter(Mandatory=$True,ParameterSetName="vdi")]
		[string]$vdi
	)
	If(!$PSBoundParameters.ContainsKey('vdi') ){
		$ComputerName=Get-XMLValue $powershell_config "//$domain/$env/$node" 
		cmdkey /delete:$ComputerName
	}else{cmdkey /delete:$vdi}
}
Function Get-VDIGroupMember(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$false)]
        [string]$cmp="localhost",
        [string]$grp="Administrators",
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
	Try{
	    If([ADSI]::Exists("WinNT://$cmp/$grp,group")){
            $members=@()
			([ADSI]"WinNT://$cmp/$grp,group").psbase.Invoke("Members")|%{
				$m = ([ADSI]$_).InvokeGet("Name") ; $p=([ADSI]$_).path ; $region=$p.Split('/')[2]
				Try{
					If([ADSI]::Exists("$p,user")){$class_name='User'}
				}Catch{
					$class_name='Group'
				}
                $members +=''|select @{n='VDI';e={$cmp}},@{n='Group';e={$grp}},@{n='Member';e={$m}},@{n='Class';e={$class_name}},@{n='Region';e={$region}},@{n='Path';e={$p}}
			}
			return $members |sort class,name
	    }else{write-Log Error "No such group ($grp) was found in computer $cmp" $log_file}
	}Catch{ write-Log Error $_ $log_file}finally{}
}
Function Set-VDIGroupMember(){
	[cmdletBinding()]Param(
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [string][Alias("cn")]$computer,
        [Parameter(Mandatory=$false)]
        [string]$group="Administrators",
        [Parameter(Mandatory=$false)]
        [ValidateSet("add", "remove")]
        [string]$act="add",
        [Parameter(Mandatory=$True)]
        [ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
        [string]$fid,
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
	$ad_group = [ADSI]("WinNT://$computer/$group")
	$ad_user = [ADSI]("WinNT://{0}/{1}" -f $fid.split('\'))
	try{
        $ad_group.PSBase.Invoke($act,$ad_user.PSBase.Path)
		$log="The user $user has been $act of group $group in computer $computer." ; $log_type="Success" ; $return_value=$true
	}catch{
		switch -regex ($_.exception){
			"group name"{$log= "The group $group cannot be found in computer $computer.";$log_type="Error";$return_value=$false ;break}
			"pathname"{$log ="The user $user cannot be found in domain $domain.";$log_type="Error";$return_value=$false ;break}
			"already"{$log ="The user $user is already added to this group $group in computer $computer.";$log_type="Warning";$return_value=$true;break}
			"not a member"{$log="The user $user is already removed from group $group in computer $computer.";$log_type="Warning";$return_value=$true;break}
            default{$log=$_ ; $log_type="Error";$return_value=$false }
		}
	}finally{Write-Log $log_type $log $log_file}
	return $return_value
}
Function Get-ADObject(){
	[cmdletBinding()]param(
        [Parameter(Mandatory=$true)]
        [ValidatePattern("^(nam|apac|eur)\\.{7}")]
		[string]$fid,
		[Parameter(Mandatory=$False)]
		[string]$property,  # memberof, userprincipalname ,manager ...
		[switch]$all, # return all the ad object properties
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
    $domain,$ad_name=$fid.split('\')
    $ad_searcher = New-Object DirectoryServices.DirectorySearcher([System.DirectoryServices.DirectoryEntry]"LDAP://dc=$domain,dc=nsroot,dc=net","(samaccountname=$ad_name)") #SearchRoot and Filter
	If(($ADObject = $ad_searcher.FindOne()) -eq $null){
	    write-log Error "No such ad object $domain\$ad_name,please double check spelling!" $log_file
		return $null
	}elseif($PSBoundParameters.ContainsKey('property')){
        return $ADObject.Properties.$property
	}elseif($all){
		return $ADObject.Properties
	}else{return $ADObject}
}
Function Set-FIDPwd(){ # have not tested this function yet,to be updated
	[cmdletBinding()]param(
        [ValidatePattern("^(nam|apac|eur)\\.{7}")]
        [string]$fid,
		[Parameter(Mandatory=$False)]
		[string]$domain="NAM",
		[Parameter(Mandatory=$True)]
		[string]$user,
		[Parameter(Mandatory=$True)]
		[string]$tempPwd,
		[Parameter(Mandatory=$False)]
		[string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
	)
	If( [ADSI]::Exists("WinNT://{0}/{1},user" -f $adobject.split('\')) ){
		try{
			$objuser = Get-ADObject $domain $user
			$objuser.psbase.invoke("SetPassword",$tempPwd )
			#0 to enable the User must change password at next logon option 
			#-1 to disable the User must change password at next logon option
			# $objuser.psbase.invokeSet("pwdLastSet",0)
			$objuser.setinfo()
			return $true
		}catch{
			return $false
		}finally{}
	}
}
Function Open-FileToEnterValue($file){
	New-IOItem $file ; notepad $file
}
Function Enable-RemotePS(){ # to be updated
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$computer,
        [Parameter(Mandatory=$False)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
	if(Test-Connection $computer -quiet -count 1 -ea 0){
		try{#add excetion filewall rule so that "Enable-PSRemoting –force" can run successfully
			& psexec.exe \\$computer netsh advfirewall firewall show rule name="Windows Remote Management (HTTP-In)" dir=in 2>$null
			if($lastExitCode -eq 1){
				& psexec.exe \\$computer netsh advfirewall firewall add rule name="Windows Remote Management (HTTP-In)" dir=in action=allow service=any enable=yes profile=any localport=5985 protocol=tcp 2>$null
				if($lastExitCode -ne 0){
					Write-Log -logType Error "Failed to add filrewall rule in $computer" $currentLog
					return $false
				}
			}elseif($lastExitCode -eq 0){#enable powershell remote PC connecting
				& psexec.exe \\$computer powershell.exe "Enable-PSRemoting -force" 2>$null
				if($lastExitCode -ne 0){
					Write-Log -logType Error "Failed to Enable-PSRemoting in $computer" $currentLog
					return $false
				}
				return $true
			}
		}catch{
			Write-Log -logType Error "Cannot connect $computer from remote powershell" $currentLog
			return $false
		}finally{}
	}else{
		Write-Log -logType Error "$computer is not online" $currentLog
		return $false
	}
}
Function Get-SoftwareReg(){ # the filter pattern needs to be optimized in the future for less time checking
	[CmdletBinding()]param(
		[Parameter(Mandatory=$True,ParameterSetName="one",Position="0")]
		[string]$software,
		[Parameter(Mandatory=$False,ParameterSetName="one")]
		[string]$version="*",
		[Parameter(Mandatory=$False,ParameterSetName="all")]
		[Switch]$all
	)
    $reg64_path='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
    $reg32_path='HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
	If($all){
        $reg64_path,$reg32_path|%{$software_reg+=(dir $_ |? {$_.name -notmatch '(\.)?KB\d+'}|Get-ItemProperty)}
		return $software_reg
	}else{
		try{
			$software_reg = dir $reg32_path |Get-ItemProperty|?{$_.DisplayVersion -like "*$version*" -and $_.displayname -like "*$software*"}
			if($software_reg -eq $null){
				$software_reg = dir $reg64_path|Get-ItemProperty|?{$_.DisplayVersion -like "*$version*" -and $_.displayname -like "*$software*"}
			}
			return $software_reg
		}catch{
			return $null
		}finally{}
	}
}
Function Enter-LeftMouse(){
    $signature=@' 
      [DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
      public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);
'@ 
	$SendMouseClick = Add-Type -memberDefinition $signature -name "Win32MouseEventNew" -namespace Win32Functions -passThru 
	$SendMouseClick::mouse_event(0x00000002, 0, 0, 0, 0); #0x00000002 corresponds to a left-mouse-down move
	$SendMouseClick::mouse_event(0x00000004, 0, 0, 0, 0); #0x00000004 corresponds to a left-mouse-up move   
}
Function Get-RAMSize([string]$by_size="gb"){
	return ((Get-WmiObject CIM_PhysicalMemory).Capacity|Measure-Object -Sum|%{"{0:0.00}" -f ($_.sum / "1$by_size")})
}
Function Get-HardDiskSize([string]$disk='c',[string]$by_size="gb"){
    return ("{0:0.00}" -f ((Get-WmiObject Win32_LogicalDisk|?{$_.DeviceID -imatch "^$disk"}).Size / "1$by_size"))
}
Function New-CustomObject($properties){ # to be updated to hashtable
	$obj=New-Object object
	$pro_list=(get-content $pro_file)|?{!$_.startswith('#')}
	try{
		foreach($line in $pro_list){
			$line=$line.split("=")
			Add-Member -InputObject $obj -Name $line[0].trim() -Value $line[1].trim() -MemberType NoteProperty
		}
		return $obj
	}catch{
		return $null
	}
}
Function Save-Screenshot_old_function(){
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [string]$save_file,
        [Parameter(Mandatory=$False)]
        [ValidateSet("BMP","JPEG","GIF","TIFF","PNG")]
        [string]$image_encoder='PNG',
        [switch]$all_screen,
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    begin {
        Add-Type -AssemblyName System.Drawing
        Add-Type -AssemblyName System.Windows.Forms
    }
    process {
        $loop_cnt=0
        Do{
            If( $all_screen ){ $send_value="{PrtSc}" #capture all the screen
            }else{$send_value="%{PrtSc}" } #capture active window screen
            [Windows.Forms.Sendkeys]::SendWait($send_value)
            sleep 1
            $bitmap = [Windows.Forms.Clipboard]::GetImage()
            If( $loop_cnt++ -gt 5 ){
                return $false
            }
        }While( $bitmap -eq $null )
        If( $bitmap -ne $null ){
            $image_codec = [Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.FormatDescription -eq $image_encoder}
            $ep = New-Object Drawing.Imaging.EncoderParameters
            $ep.Param[0] = New-Object Drawing.Imaging.EncoderParameter ([System.Drawing.Imaging.Encoder]::Quality, [long]100)
            if(test-path $save_file -ea 0){remove-item $save_file -Force -ea 0}
            Try{
                $bitmap.Save($save_file, $image_codec, $ep) ; return $true
            }Catch{
                Write-Log error $_.exception $log_file ; return $false
            }Finally{}
        }
    }
}
Function Save-Screenshot($save_file){
    # sendkey & screenshot not working when the user screen is locked
    [void][Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    $bounds = [Drawing.Rectangle]::FromLTRB(0, 0, 1920, 1080)
    $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
    $graphics = [Drawing.Graphics]::FromImage($bmp)
    $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
    $bmp.Save($save_file) 1>$null
    $graphics.Dispose() 1>$null
    $bmp.Dispose() 1>$null  
}
Function Save-OpenedRDCScreen($save_directory="$home\Pictures" ){
    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
	del "$home\Pictures\*.png" -Force -ErrorAction Ignore
	Foreach( $rdc in (Get-OpenedRDC) ){
        [Microsoft.VisualBasic.Interaction]::AppActivate($rdc); sleep 2
		Save-Screenshot "$save_directory\$rdc.png"
	}
}
Function Get-IEObject([Switch]$log){ # to be updated
	$ie=$null
	Foreach( $i in (New-Object -ComObject Shell.Application).Windows()){
	    If($i.Name  -like "*Internet Explorer*"){
	       $ie=$i ; break #$ie.FullScreen=$true
	    }
	}
	If( $ie -eq $null ){
		$start=get-date
		do{
			$error.clear()
			try{
				$ie=new-object -com InternetExplorer.Application
			}catch{
				$runningSeconds = ((get-date) -$start).TotalSeconds
				if($runningSeconds -gt 10){
					if($log){Write-Log -logType Error "Waiting too long(10s) for IE open" $log} return $null
				}
			}finally{}
		}while($error.count -ne 0)
	}
    $asm = [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds|select Width,Height
    $ie.width,$ie.height=$screen.Width,$screen.Height
    $ie.Top =  0 ;   $ie.Left = 0
	$ie.visible = $true ;$ie.MenuBar=$false
	return $ie
}
Function Get-OutlookInbox(){
	Try{
		Add-Type -assembly "Microsoft.Office.Interop.Outlook"
		$Outlook = New-Object -comobject Outlook.Application
		$namespace = $Outlook.GetNameSpace("MAPI")
		$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
		return $inbox
	}Catch{
	    return $null
	}Finally{}
}
Function Get-OutLookMails(){
	[CmdletBinding()]
    param(
		[Parameter(Mandatory=$True)]
		[__ComObject]$mail_folder,
		[Parameter(Mandatory=$False)]
		[ValidateScript({($_|Get-Date) -lt (get-date)})]
		[string]$from=(get-date).ToString("MM/dd/yyyy"),
		[Switch]$Recurse
	)
	$mail_folderPath = ($mail_folder.FullFolderPath -split "com\\")[1]
	write-host "Checking mail in folder $mail_folderPath"
	$mails=@()
    Foreach($item in $mail_folder.Items){
		If(($itime=$item.ReceivedTime) -ne $null){
			if($itime -gt ([DateTime]$from)){
				Add-Member -InputObject $item -Name InFolder -Value $mail_folderPath -MemberType NoteProperty
				$mails+=$item
			}else{break}
		}
	}
	If( $Recurse ){
		Foreach( $folder in $mail_folder.Folders){
			If($folder.Folders.Count -ne 0){
				Get-OutLookMails $folder -Recurse
			}else{
				Get-OutLookMails $folder
			}
		}
	}
	return $mails
	# $inbox.Items|select -First 100|Group-Object -Property Subject -NoElement | Sort-Object count
}
Function Send-MailToTeam(){ # this is a template funtion
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$error_info
    )
$body = @"
<p>Hi RPA AA STE Support,</p>
<p>An error was found while monitoring file modified date with below details:</p>
<table border="1">
<tr><td>Monitor File</td><td>$to_be_checked_file</td></tr>
<tr><td>Expected $match_property</td><td>$match_property_value</td></tr>
<tr><td bgcolor="#FF0000">Error</td><td bgcolor="#FF0000">$error_info</td></tr>
</table>
<p>Thank You!</p>
aaadmin
"@
    $this_user=$env:username
    $this_computer=$env:computername
    $this_env=Switch -regex ( $this_computer.Substring(7,3)){                    # this computer environment
        "p2n"               {"PROD";break}
        "u2n"               {"UAT" ;break}
        "-f2|-18|-aa|-50"   {"DEV" ;break}
        "-44|-97|-c2"       {"POC" ;break}
        "-f3|-3d|-c5"       {"LAB" ;break}
        "c2n"               {"COB" ;break}
        "e2n"               {"PTE" ;break}
        default             {$this_computer}
    }
    $this_node=Switch -regex ($this_computer.substring($this_computer.length -7)){
        "((^ap.*01)|(49|E2|9a))$"  {"1";break}
	    "(02|b8|0e|ae)$"           {"2";break}
	    "(03|72|27|50)$"           {"3";break}
	    "(26|04|27|fe)$|(db.*01$)" {"4"      }
    }
    $this_env=("[$this_env-{0}-$this_node]" -f (Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0]).toupper()
    Send-MailMessage `
        -From "$this_user.$this_computer@imcnam.ssmb.com" `
        -To dl.gt.cn.cstc.rpa.aa.ste@imcnam.ssmb.com `
        -Subject "$this_env RPA AA STE File Last Modify Time Monitor - ERROR" `
        -Body $body -BodyAsHtml -Priority High `
        -SmtpServer "imta.citicorp.com" -Port 25 `
        -Encoding([System.Text.Encoding]::UTF8)
}
Function Send-OutlookEmail(){ # to be updated
	[cmdletbinding()]
	Param (
		[Parameter(Mandatory=$False,Position=0)]
		[String]$To="cw50406@imcnam.ssmb.com",
		[Parameter(Mandatory=$False,Position=1)]
		[String]$Subject="test email from powershell",
		[Parameter(Mandatory=$False,Position=2)]
		[String]$Body="test email from powershell"
	)
	Begin {
		Clear-Host ; Add-Type -assembly "Microsoft.Office.Interop.Outlook"
	}
	Process {
		# Create an instance Microsoft Outlook
		$Outlook = New-Object -ComObject Outlook.Application
		$Mail = $Outlook.CreateItem(0)
		$Mail.To = "$To"
		$Mail.Subject = $Subject
		$Mail.Body =$Body
		# $Mail.HTMLBody = "When is swimming?"
		# $File = "D:\CP\timetable.pdf"
		# $Mail.Attachments.Add($File)
		$Mail.Send()
        write-host "mail sent"
	} # End of Process section
	End {
		# Section to prevent error message in Outlook
		# $Outlook.Quit()
		[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Outlook)
		$Outlook = $null
	} # End of End section!
}
Function Connect-DB(){
	[CmdletBinding()]param(
		[Parameter(Mandatory=$True)]
		$DBServer,
		$DBPwd,
		[Parameter(Mandatory=$False)]
		$DBS= "ICGDB",
		$log_file="$home\Documents\WindowsPowerShell\db_log.txt"
	)
	$connection = New-Object System.Data.SqlClient.SqlConnection
	$connection.ConnectionString = "Server=$DBServer;uid=aa_dbread;pwd=$DBPwd;Database=$DBS;Integrated Security=False;"
	Try{
	   $connection.Open() ; return $connection
	}Catch{
	   Write-Log Error $_.exception $log_file ; return $null
	}Finally{}
}
Function Close-DB($connection,$log_file="$home\Documents\WindowsPowerShell\db_log.txt"){
    Try{
        $connection.close()
    }Catch{
        write-Log Error $_.exception $log_file
    }Finally{}
}
Function Execute-Sql(){
    param(
        [Parameter(Mandatory=$True)]
        $connection,
        [Parameter(Mandatory=$False)]
        [string]$query,
        [int]$timeout=30,
        [string]$log_file
    )
    Try{
        $sqlcmd=new-object system.data.sqlclient.sqlcommand($query,$connection)
        $sqlcmd.CommandTimeout = $timeout
        $adapter = New-Object System.Data.sqlclient.sqlDataAdapter ($sqlcmd)
        $dataset = New-Object System.Data.DataSet ; $adapter.Fill($dataSet) | Out-Null
        return $dataSet.Tables[0]
    }Catch{
        write-log Error $_.Exception.Message $log_file ; return $null
    }Finally{}
}
Function Get-CRTaskResultFromDB(){ # to be updated
    [CmdletBinding()]
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$DBServer,
    	[string]$DBPwd,
    	[string]$user,
    	[string]$task,
    	[string]$taskPath,
    	[string]$cmp,
		[Parameter(Mandatory=$False)]
    	[string]$log_file="$home\Documents\WindowsPowerShell\db_log.txt"
    )
	If(($connection = Connect-DB $DBServer $DBPwd -logfile $log_file) -ne $null){
		$uquery = "SELECT id  FROM [ICGDB].[dbo].[Users] where UserName = '$user' and IsDeleted=0"
		If( ($utable  = Execute-Sql $connection $uquery $log_file) -ne $null ){# check if FID exists in CR
			$userId = $utable.ID
			$tquery = "SELECT id FROM [ICGDB].[dbo].[Tasks] where FileName='$task' and RelativePath like '%$taskPath'"
			$cquery = "SELECT id FROM [ICGDB].[dbo].[Clients] where HostName='$cmp' and UserId='$userId'"
			If((($ttable  = Execute-Sql $connection $tquery $log_file) -ne $null) -and (($ctable = Execute-Sql $connection $cquery $log_file) -ne $null)){# check if task exists in CR
				$taskId=$ttable.ID ; $ClientId = $ctable.ID
				If( $taskId.GetType().Name -eq "Object[]" ){
				    write-error "Have found bulk task ID for $task,please re-edit and run again" $log_file ; return $false}
				$trdquery="
					SELECT StartTime FROM [ICGDB].[dbo].[TaskRunDetails] where 
					ClientId='$ClientId' and TaskId='$taskId' and Status='Completed' and IsTaskExecutionCompleted=1
				"
				$aqurey="
					SELECT Status FROM [ICGDB].[dbo].[Activities] where UserName='$user' and 
					ActivityType='Run Task' and ActivityAt > CONVERT(VARCHAR(30),DATEADD(MINUTE,210,getdate()),120) --the default duration is within 30 mins
					and charindex('$task',EventDescription)>0 and charindex('Run Duration',EventDescription)>0
				"
				If((($trdtable  = Execute-Sql $connection $trdquery $log_file) -ne $null) -and (($atable= Execute-Sql $connection $aqurey $log_file) -ne $null)){
					If( $atable.Status -ieq "Success" ){
						$TaskRunDetails=@()
						If( $trdtable.GetType().Name -eq "DataRow" ){$TaskRunDetails+=($trdtable.StartTime) # only returns one line of data
						}elseif( $trdtable.GetType().Name -eq "Object[]" ){$TaskRunDetails+=$trdtable|%{$_.StartTime}}# returns bulk lines of data
						Write-Log Success "Have found the completed task $task ran in $cmp" $log_file
						$connection.close() ; return $TaskRunDetails
					}else{ Write-Log Error "Task $task failed running in VDI $cmp" $log_file	}
				}else{Write-Log Error "Task $task was not found running in VDI $cmp" $log_file}
			}else{Write-Log Error "Task $task or VDI $cmp was not found in $domain\$env control room" $log_file}		
		}else{Write-Log Error "FID $domain\$user was not found in $domain\$env control room" $log_file}
		Close-DB $connection; return $false
	}
}
Function Get-CRUserFromDB(){ # to be updated
    [CmdletBinding()]
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$DBServer,
    	[string]$DBPwd,
    	[string]$user,
    	[Parameter(Mandatory=$false)]
    	[Array]$role,
    	[Array]$licence,
        $log_file="$home\Documents\WindowsPowerShell\db_log.txt"
    )
	If(($connection = Connect-DB $DBServer $DBPwd -logfile $log_file) -ne $null){
		$uquery = "SELECT id  FROM [ICGDB].[dbo].[Users] where UserName = '$user' and IsDeleted=0"
		If( ($utable  = Execute-Sql $connection $uquery $log_file) -ne $null ){# check if FID exists in CR
			$userId = [String]$utable.ID
			$rquery = "
				DECLARE @t TABLE(RoleName varchar(50)) -- difine table to store results
				DECLARE @RoleId varchar(50) -- define curser
				DECLARE My_Cursor CURSOR
				FOR SELECT RoleId FROM [ICGDB].[dbo].[UserRoles] where userid ='$userId' -- put the clollections in the curser
				OPEN My_Cursor;
				FETCH NEXT FROM My_Cursor INTO @RoleId; -- fetch the 1st line in RoleId in the curser
				WHILE @@FETCH_STATUS = 0
					BEGIN
						insert into @t values((SELECT name FROM [ICGDB].[dbo].[Roles] where id=@RoleId))
						FETCH NEXT FROM My_Cursor INTO @RoleId; -- fetch the next line in RoleId in the curser
					END
				CLOSE My_Cursor; --close cursor
				DEALLOCATE My_Cursor; --release curser
				select * from @t
			"
			# If(($rtable  = Execute-Sql $connection $rquery $log_file) -ne $null ){# check if FID has roles in CR
				$rtable  = Execute-Sql $connection $rquery $log_file # if FID exist in CR, it must has roles
				$foundRole=@()
				If( $rtable.GetType().Name -eq "DataRow" ){$foundRole+=$rtable.RoleName # only returns one line of data
				}elseif( $rtable.GetType().Name -eq "Object[]" ){$rtable|%{$foundRole+=$_.RoleName}}# returns bulk lines of data
				$lquery = "SELECT Feature FROM [ICGDB].[dbo].[Features] where userid='$userId'"
				If( ($ltable  = Execute-Sql $connection $lquery $log_file) -ne $null ){# check if FID has licences
					$foundLicence=@()
					If( $ltable.GetType().Name -eq "DataRow" ){$foundLicence+=$ltable.Feature # only returns one line of data
					}elseif( $ltable.GetType().Name -eq "Object[]" ){$ltable|%{$foundLicence+=$_.Feature}}# returns bulk lines of data
				}else{$foundLicence="No licences"}
				# Write-Log Error "FID $domain\$user-$foundRole has no licences in $domain\$env control room" $log_file;
				# If( ![string]::IsNullOrWhiteSpace($role) ){
					# if((Compare-Object -ReferenceObject $role -DifferenceObject $foundRole ) -eq $null){$roleMatch=$true}else{$roleMatch=$false}
				# }
				
					# If(![string]::IsNullOrWhiteSpace($licence)){
						# if((Compare-Object -ReferenceObject $licence -DifferenceObject $foundLicence) -eq $null){$licenceMatch=$true
						# }else{$licenceMatch=$false}
						# If( $roleMatch -and $licenceMatch){
							# Write-Log Success "FID $domain\$user has exact same roles and licences" $log_file 
							# $connection.close() ; return $true
						# }elseif(![string]::IsNullOrWhiteSpace($role) -or ![string]::IsNullOrWhiteSpace($licence)){
							# $foundRole|%{$fr+=($_+",")} ; $foundLicence|%{$fl+=($_+",")}
							# Write-Log Warning "FID $domain\$user-$foundRole-$foundLicence-not exactly match the given values." $log_file 
						# }
					# }elseif($roleMatch){}
			# }else{Write-Log Error "FID $domain\$user has no roles in $domain\$env control room" $log_file}
		}else{Write-Error "FID $domain\$user was not found in $domain\$env control room!"}
		$connection.close() ; return "$domain\$user-$foundRole-$foundLicence"
	}
	# Development
	# FuncRuntime
	# FuncRuntime,MetabotRuntime
}
Function Backup-JavaFiles($javaReg,$oldFile,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	$str = switch -regex ($oldFile){
		"jar$"        {"lib\ext";break}
		"dll$"        {"bin";    break}
		"properties$" {"lib";         }
		}
	$oldFile =$javaReg.InstallLocation + "$str\$oldFile"
	try{
		if(test-path $oldFile){rename-item $oldFile "_"+$oldFile -force}
		else{Write-Log Error "Unable to find the file $oldFile" $log_file}
	}catch{
		Write-Log Error "Unable to rename the java file" $log_file
	}finally{}
}
Function Copy-AAFileToJavaFolder($AAReg,$javaReg,$file,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	$sourcePath = $AAReg.InstallLocation+$file
	$targetpath = $javaReg.InstallLocation
	if(test-path $AAReg.InstallLocation){
		$str=switch -regex ($file){
			"jar$"        {"lib\ext";break}
			"dll$"        {"bin"    ;break}
			"properties$" {"lib"          }
		}
		copy-item -force $sourcePath "$targetpath\$str"
	}else{Write-Log Error "Unable to find the file $oldFile" $log_file}
}
Function Add-EnvPath($computer,$path,$log_file="$home\Documents\WindowsPowerShell\log.txt"){ # to be updated
	if($env:path -like "*$path*" ){
		Write-Log Normal "The path: $path is already in the env Path in $($env:computername)" $log_file
	}else{
		if(test-path $path -ea 0){
			$NewEnvpath = $env:path + ";" + $path
			Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name PATH -Value $NewEnvpath
		}else{
			Write-Log Error "The $path doesn' exist in $env:computername" $log_file
		}
	}
}
Function Clear-JavaCahe(){
	$jDeploy="$home\AppData\LocalLow\Sun\Java\Deployment"
	try{
		"$jDeploy\cache\6.0","$jDeploy\ext","$jDeploy\log"|%{del "$_\*" -force -ea 0}
	}catch{}finally{}
}
Function Set-DefaultBrowser($defaultBrowser="ie"){
    $regKey= "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\{0}"
    $regKeyFtp= $regKey -f 'ftp' ;$regKeyHttp   = $regKey -f 'http' ;$regKeyHttps  = $regKey -f 'https'
    $regKeyFtp,$regKeyHttp,$regKeyHttps|%{ # check if the registry key/value exist and then create it 
        $path=$_
        If( !(test-path $path -ea 0) ){
            new-item -path $path | Out-Null
            new-item -path $path\UserChoice | Out-Null
        }
    }
    switch -Regex ($defaultBrowser){
        'ie|internet|explorer' {$regValuFtp= "IE.FTP" ; $regValuHttp= "IE.HTTP" ; $regValuHttps= "IE.HTTPS"} # Internet Explorer
        'ff|firefox' {$regValuFtp=$regValuHttp=$regValuHttps = "FirefoxURL"} # Firefox
        'cr|google|chrome' {$regValuFtp=$regValuHttp=$regValuHttps ="ChromeHTML"} # Google Chrome
        'sa*|apple' {$regValuFtp=$regValuHttp=$regValuHttps ="SafariURL"} # Safari
        'op*' {$regValuFtp=$regValuHttp=$regValuHttps ="Opera.Protocol"}  # Opera
    } 
    Set-ItemProperty $regKeyFtp\UserChoice   -name ProgId $regValuFtp
    Set-ItemProperty $regKeyHttp\UserChoice  -name ProgId $regValuHttp
    Set-ItemProperty $regKeyHttps\UserChoice -name ProgId $regValuHttps
}
Function Move-Cursor($x,$y){
    [void][System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
	[windows.forms.cursor]::Position = New-Object System.Drawing.Point($x,$y)
}
Function Wait-ElementTo(){# to be updated
	param(
		[Parameter(Mandatory=$true)]
		[__ComObject]$ie,
		[Parameter(Mandatory=$false,ParameterSetName="id")]
		[string]$id,
		[Parameter(Mandatory=$false,ParameterSetName="tag")]
		[string]$name,
		[Parameter(Mandatory=$true,ParameterSetName="tag")]
		[string]$tag,
		[Parameter(Mandatory=$false)]
		[ValidateSet("Appear", "Disappear")]
		[string]$action="Appear"
	)
	$cnt=0
	If($PSBoundParameters.ContainsKey('id')){
		Try{
			$element = $ie.Document.IHTMLDocument3_getElementByID($id)
			Switch($action){
				"Appear"{$cmdlets="$element.GetType().name -ne '__ComObject'"}
				"Disappear"{$cmdlets="$element.GetType().name -eq '__ComObject'"}
			}
			while(iex $cmdlets){
				Start-Sleep -Milliseconds 200;$cnt++
				If($cnt -ge 50){
					Write-Log Error "Wait too long (10s) for the element $action in IE page" $log_file
					return $false
				}
				$element = $ie.Document.IHTMLDocument3_getElementByID($id)
			}
			return $true
		}Catch{
		    If( $action -ieq "Disappear" ){	return $true }
		}Finally{}
	}elseif($PSBoundParameters.ContainsKey('name')){
		do{
			$foundEL=$false ; $elements=$ie.Document.IHTMLDocument3_getElementsByTagName($tag)
			foreach($e in $elements){
				Switch($action){
					"Appear"{if($e.innertext -eq $name){return $true}else{$foundEL= $true}}
					"Disappear"{if($e.innertext -eq $name){$foundEL= $true ; break}}
				}
			}
			Start-Sleep -Milliseconds 200 ; $cnt++
			If($cnt -ge 50){
				Write-Log Error "Wait too long (10s) for the element $action in IE page" $log_file
				return $false
			}
		}while($foundEL)
		return $true
	}
}
Function Wait-CRUserCreatePageLoad(){# to be updated
	param(
		[Parameter(Mandatory=$True)]
		[__ComObject]$ie,
		[Parameter(Mandatory=$False)]
		[string]$elName="User Management",
		[string]$crUrl,
		[string]$log_file="$home\Documents\WindowsPowerShell\cr_log.txt"
	)
    $ie.Navigate($crUrl) ; while ($ie.Busy -or $ie.ReadyState -ne 4) { Start-Sleep -Milliseconds 200}
	Active-Window $ie.LocationName $log_file 10 1>$null; Wait-ElementTo -ie $ie -name $elName -tag "span" 1>$null; 
	Active-Window $ie.LocationName $log_file 10 1>$null
	1..8|%{Start-Sleep -Milliseconds 200;send-keys "tab"} ; send-keys "{DOWN 3}" ; send-keys "{TAB 2}" ; send-keys "enter" ; send-keys "enter"
	return $true
}
Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID } ----------------------> to be updated
	# useing parallel threads to run scripts in the same time so that it will speedup the running duration
	#reference https://www.codeproject.com/tips/895840/multi-threaded-powershell-cookbook
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             # the vdi list that scripts to be ran
    	[ScriptBlock]$Action,           # the actual action that each thread will run
    	[AllowNull()]
    	[Object[]]$ActionParams,        # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$max_threads_count=100,    # the default amount of powershell threads, if this number is big, then the system might get stuck
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $total_thread_return_result = New-Object System.Collections.ArrayList
	If( $vdilist.Count -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-log Error "vdilist only contains 1 line but it's null" $log_file
		}else{
            Try{
                [void]$total_thread_return_result.Add((& $Action $vdilist[0] 1 $ActionParams))
            }Catch{
                write-log Error $_.Exception.Message $log_file
            }Finally{}
        }
	}else{
		If( $vdilist.Count -le $max_threads_count ){ $max_threads_count = $vdilist.Count }
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $max_threads_count, $SessionState, $Host)
		$Pool.Open() ; $threads = New-Object System.Collections.ArrayList ; $cnt=1
        $handles = @()
		foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
            write-verbose "Starting to create $cnt thread with param <<$line>>" -verbose
			$powershell = [powershell]::Create().AddScript($Action).AddArgument($line).AddArgument($cnt).AddArgument($ActionParams)
			$powershell.RunspacePool = $Pool ; $handles+=$powershell.BeginInvoke() ; $threads.Add($powershell) | Out-Null; $cnt++
		}
		do{ $done = $true # wait each thread to be done and then collect its return value
            $complete_percent=[math]::round((($handles.count - ($handles | Where-Object {$_.IsCompleted -eq $False}).count ) / $handles.Count * 100),2)
            Write-Progress `
            -Activity "Waiting for $($max_threads_count - $($Pool.GetAvailableRunspaces())) of $max_threads_count threads running" `
            -PercentComplete $complete_percent `
            -Status "$complete_percent completed" 
            For( $i=0; $i -lt $handles.count; $i++ ){
                if($handles[$i] -ne $null){
                    if($handles[$i].IsCompleted){
                        write-verbose ("The $($i+1) thread with param <<{0}>> has been completed" -f $threads[$i].Commands.Commands.Parameters[0].value) -verbose
                        If( ($single_thread_result=$threads[$i].EndInvoke($handles[$i])) -ne $null){ # return PSDataCollection<PSObject>
                            [void]$total_thread_return_result.Add($single_thread_result[0])
                        }
                        $threads[$i].Dispose(); $threads[$i] = $null; $handles[$i]=$null
                    }else{$done = $false}
                }  
            }
            Start-Sleep -Milliseconds 200
		} until ($done)
        $threads=$null ; $handles=$null
        'threads','handles'|%{Remove-Variable $_ -Force -ea 0}
        $Pool.close()|out-null ; $Pool.Dispose() | Out-Null
	}
    return $total_thread_return_result
}
Function Copy-File(){ # to be updated
	[CmdletBinding()]
    param(
		[Parameter(Mandatory=$True)]
		[string]$vdi_list_file,
		[Parameter(Mandatory=$True)]
		[string]$source_file_list,
		[Parameter(Mandatory=$True)]
		[string]$end_folder="c$\temp\"
	)
	$copy_action={
		param($vdi,$cnt,$action_params)
		$source_file_list,$end_folder=$action_params
		Try{
			Foreach( $f in $source_file_list){
				write-host "copy $f to $cnt-$vdi ..."
				copy $f \\$vdi\$end_folder -force
			}
		}Catch{
		    $_
		}Finally{}
	}
	$vdi_list=(get-content $vdi_list_file |select -Unique|?{$_.trim() -ne ""}|sort)
	Start-BulkThreads $vdi_list $copy_action $source_file_list,$end_folder
}
Function New-ScheduledTask(){
    #This function is used because when using SCHTASKS to create the task, the single quote  ' will always be forced transferred to be double quote "
    # paramters using
    # $task_arg="-WindowStyle Hidden -Executionpolicy unrestricted `"test.ps1 `'C:\Program Files (x86)\Java`'`""
    # -run_task_user 'System'                                                          ==> use system account to run this task
    # -run_task_user 'System' -runas                                                   ==> use system account to run this task with highest privileges
    # -run_task_user 'nam\aaadmin' $run_task_user_pwd 'xxxxxxx' -run_login_only        ==> use specified account to run this task
    # -run_task_user 'nam\aaadmin' $run_task_user_pwd 'xxxxxxx' -run_login_only -runas ==> use specified account to run this task with highest privileges
    # -task_arg "-noexit -Executionpolicy unrestricted `"c:\temp\test.ps1 `'C:\Program Files (x86)\Java`'`"" -run_task_user 'nam\cw50406' -run_login_only
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$False)]                  
        [string]$task_name='temp_task',                         # the task name to be created and invoked later
        [string]$task_executor= "powershell.exe",               # the main task file or exe to be ran
        [AllowNull()][string]$task_arg,                         # the arguments and parameters for the $task_command
        [string]$run_task_vdi="localhost",                      # the vdi to be ran the task
        [string]$run_task_user='System',                        # the user who will run the task, default is System account
        [object]$run_task_user_pwd=$null,                       # the password for user running, default is null for system or login running only tpye
        $log_file="$home\Documents\WindowsPowerShell\log.txt",  # the log file for log info store
        [switch]$run_login_only,                                # run task only when the user has logged in the VDI, must be true for non-system account
        [switch]$runas                                          # run task with highest privileges
    )
    #<--------------------------------- create and connect remote VDI Schedule Service
    Try{
        $task_schedule_service = new-object -ComObject("Schedule.Service")
        Try{
            $task_schedule_service.Connect($vdi)
            #<--------------------------------- setup task definition
            $task_definition = $task_schedule_service.NewTask(0) 
            $task_definition.Settings.Enabled = $true
            $task_definition.Settings.AllowDemandStart = $true
            #<--------------------------------- setup actual task actions
            $task_action = $task_definition.Actions.Create(0)
            $task_action.Path = $task_executor
            $task_action.Arguments = $task_arg
            #<--------------------------------- setup task trigger to be once ran 
            $task_triggers = $task_definition.Triggers
            $task_trigger = $task_triggers.Create(1)
            $task_trigger.StartBoundary = "2100-01-01T00:00:00"
            $task_trigger.Enabled = $true
            #<--------------------------------- register the task in remote VDI with given account(default is System account)
            Try{
                $root_folder = $task_schedule_service.GetFolder("\")
                $logon_type=Switch($run_login_only){$true{3;break}$false{5}}
                $root_folder.RegisterTaskDefinition($task_name,$task_definition,6,$run_task_user,$run_task_user_pwd,$logon_type) 1>$null
                If( $runas ){
                    $error.clear()
                    If( SCHTASKS /QUERY /S $run_task_vdi /TN $task_name 2>$null){ #<----------------------check if task has been created
                        $run_task_user_pwd|SCHTASKS /Change /S $run_task_vdi /RL HIGHEST /tn $task_name 1>$null 2>&1
                        #<----------------------check if task has been changed to be ran with highest privileges
                        If( (SCHTASKS /QUERY /TN temp_task /XML 2>$null) -imatch 'HighestAvailable'){
                            return $true
                        }else{
                            write-log Error $error $log_file ; return $false
                        }
                    }else{ write-log Error 'Not found task $task_name to set its running as admin' $log_file ; return $false}
                }else{return $true}
            }Catch{write-log Error $_ $log_file ; return $false}Finally{}
        }Catch{write-log Error $_ $log_file ; return $false}Finally{}
    }Catch{write-log Error $_ $log_file ; return $false}Finally{}
}
Function Get-VDIloggedUser( ){
    [CmdletBinding()]
    param(
    	[Parameter(Mandatory=$True)]
    	[string]$fid,
    	[string]$vdi,
        [Parameter(Mandatory=$false)]
        $log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
	If( Assert-AdminRight ){
        Get-Service RemoteRegistry -cn $vdi|?{$_.Status -ieq "Stopped"}|Start-Service
        Do{
            $rst=(Get-Service RemoteRegistry -cn $vdi).Status ; sleep -Milliseconds 200 
        }While( $rst -ine 'running' )
        reg add "\\$vdi\HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v AllowRemoteRPC /t Reg_Dword /d 0x1 /f 1>$null 2>&1
        return (quser $fid /server:$vdi 2>$null)
	}else{ write-log Error "Admin right is required to run this function." $log_file }
}
Function Get-LogonHistory{
	[CmdletBinding()]Param(
		[Parameter(Mandatory=$false)]
		[string]$Computer = $env:computername,
		[Parameter(Mandatory=$false)]
		[int]$Days = 3
	)
	$Result = @()
	Write-Host "Gathering Event Logs on computer $Computer in $Days days, this can take awhile..."
	$ELogs = Get-EventLog System -Source Microsoft-Windows-WinLogon -After (Get-Date).AddDays(-$Days) -ComputerName $Computer
	If ($ELogs){ 
		Write-Host "Processing..."
		ForEach ($Log in $ELogs){
			If ($Log.InstanceId -eq 7001){ $ET = "Logon"
			}ElseIf ($Log.InstanceId -eq 7002){ $ET = "Logoff"
			}Else{ Continue}
			$Result += New-Object PSObject -Property @{
				Time = $Log.TimeWritten
				'Event Type' = $ET
				User = (New-Object System.Security.Principal.SecurityIdentifier $Log.ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount])
			}
		}
		$Result=$Result | Select Time,"Event Type",User | Sort Time -Descending | Out-GridView
        return $Result
		Write-Host "Done."
	}Else{ 
		Write-Host "Problem with $Computer."
		Write-Host "If you see a 'Network Path not found' error, try starting the Remote Registry service on that computer."
		Write-Host "Or there are no logon/logoff events (XP requires auditing be turned on)"
	}
}
Function Get-Resolution([Switch]$all){
    # Get-WmiObject -Class Win32_DesktopMonitor | Select-Object ScreenWidth,ScreenHeight
    # (Get-WmiObject -Class Win32_VideoController).VideoModeDescription
    # Get-WmiObject -class "Win32_DisplayConfiguration"
    Add-Type -AssemblyName System.Windows.Forms
	$rst=[System.Windows.Forms.Screen]::AllScreens
	If( $all){
		$rst|%{
			If( $_.Primary -ieq "true" ){
				$primary="True"
			}else{
				$primary="False"
			}
			$_.Bounds|select @{n='Primary';e={$primary}},Width,Height
		}
	}else{
	    $rst|?{$_.Primary -ieq "true"}|%{$_.Bounds|select Width,Height}
	}
}
Function Get-RegistryKey( $regkey,$temp_csv_file,[switch]$recurse){ # to be optimized
	If( $regkey.Substring($regkey.Length-1) -eq "\"){$regkey=$regkey.Substring(0,$regkey.Length-1)}
	$regkey=$regkey `
		-replace("^HK(EY_)*CU(RRENT_USER)*:*","HKU:\$sid") `
		-replace("^HK(EY_)*L(OCAL_)*M(ACHINE)*:*","HKLM:") `
		-replace("^HKEY_USERS","HKU:") `
		-replace("^HKEY_LOCAL_MACHINE","HKLM:")
	If($regkey -inotmatch "International\\Scripts|internet explorer\\vbs|SearchScopes|Suggested Sites|TypedURLs|TypedURLsTime|Update.*DateTime|AboutURLs|FavoriteUrls|UrlTemplate|" ){
		Foreach( $pro in (get-item $regkey).Property){
			$value=[String]::join(",",(Get-ItemPropertyValue $regkey $pro))
			$csv_regkey=$regkey.replace("HKU:\$sid","HKCU:")
			"`"IE`",`"$csv_regkey\$pro`",`"$value`"" |Out-File -FilePath $temp_csv_file -Append -Encoding utf8 -Force
		}
        If( $recurse ){
            Foreach( $key in (dir -LiteralPath $regkey).name){
                Get-RegistryKey $key $temp_csv_file -recurse
            }
        }
	}
}
Function Set-FileAccess(){ # this function is used to add given user access to the given path
    [CmdletBinding()]param(
    	[Parameter(Mandatory=$True)]  
		[ValidateScript({test-path $_})]
    	[string]$path,                   # the given path to add access
    	[ValidatePattern("^(nam|apac|eur)\\.{7,9}")]
        [string]$user,                   # the given user to be added access to the given path
    	[Parameter(Mandatory=$False)]    
    	[string]$access="Modify"         # the access type to be added, default is "Modify"
    )
	$Acl = (Get-Item $path).GetAccessControl('Access')
	$InheritanceFlags="None" ; $PropagationFlags="None"
	If( (Get-Item $path).psiscontainer){
	    $InheritanceFlags="ObjectInherit"
	}
	$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($user, $access,$InheritanceFlags, $PropagationFlags, 'Allow')
	$Acl.SetAccessRule($Ar)
	Set-Acl -path $path -AclObject $Acl
}
Function Update-Inventory(){# to be updated  aaadmin has been removed access to this link 
    copy "\\sd-1423-49dd\test\alex\RPA Inventory_v6.xlsb" "C:\Users\cw50406\Downloads\RPA Inventory_v6.xlsb" -force -ea 0
    # param(
		# [Parameter(Mandatory=$false)]
		# [string]$inventory_url='https://globalconsumer.collaborationtools.consumer.citigroup.net/sites/ISG/ISGRPA/Shared%20Documents/Hardware%20Plans/RPA%20Inventory_v6.xlsb',
		# [Parameter(Mandatory=$False)]
		# [string]$local_folder="C:\Users\cw50406\Downloads\RPA Inventory_v6.xlsb",
		# [Parameter(Mandatory=$False)]
        # $log_file="$home\Documents\WindowsPowerShell\log.txt"
	# )
	# $webclient=New-Object System.Net.WebClient
    # $webclient.Credentials= New-Object System.Net.NetworkCredential('nam\aaadmin','xxxx')
    # Try{
        # $webclient.DownloadFile($inventory_url,$local_folder)
    # }Catch{
        # write-log Error $_.Exception.Message $log_file
    # }Finally{}
    # Start-BitsTransfer -Source "https://globalconsumer.collaborationtools.consumer.citigroup.net/sites/ISG/ISGRPA/Shared%20Documents/Hardware%20Plans/RPA%20Inventory_v6.xlsb","C:\Users\cw50406\Downloads\RPA Inventory_v6.xlsb" -Credential [System.Net.CredentialCache]::DefaultCredentials -Destination "C:\Users\cw50406\Downloads\RPA Inventory_v6.xlsb"
}
Function Send-MessageToRemoteComputer(){
    param(
		[Parameter(Mandatory=$True)]
		[string]$detail,
		[Parameter(Mandatory=$false)]
		[string]$user='aaadmin',
		[string]$message="I'm going to use this computer,please let me know whether it's available, thanks.`n`ncw50406(Chaoyue Wang)",
		[Switch]$websocket,
		[Switch]$database
	)
	$domain=Switch($detail[0]){
	    "N"{"NAM";break}
	    "A"{"APAC";break}
	    "E"{"EMEA"}
	}
	$env=Switch ($detail[1]){
	    "P"{"PROD";break}
	    "U"{"UAT";break}
	    "D"{"DEV";break}
	    "O"{"POC";break}
	    "T"{"PTE"      }
	}
    $ComputerName=(Get-XMLValue $powershell_config "//$domain/$env/$node").split(',')[0]
	MSG $user /SERVER:$ComputerName /TIME:10 /V /W $message
}
Function Get-Assembly(){
    return [appdomain]::currentdomain.getassemblies()
}
Function Load-Dll([ValidateScript({test-path $_})]$dll_file = ".\CredentialValidate.dll"){
    # directly load from dll file will lock the dll file so we use below method to load dll, meanwhile the dll file can be modified
    If( [appdomain]::currentdomain.getassemblies().fullname -imatch (gi $dll_file).VersionInfo.OriginalFilename.split('.')[0] ){
    }else{[void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes($dll_file))}
}
Function Get-IOSize (){ 
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$path=$pwd,
        [string]$size_unit='MB'
    )
    # method 1:  to be updated , seems must be running as admin
    # [string]$result = robocopy /b /l /mir $directory $env:TEMP /r:0 /w:0 /ns /nc /nfl /ndl /njh /bytes
    # if (!($lastexitcode -eq 16)){
    # $pos = ($result).indexof("Bytes : ") # 字节
    # $start = $pos + 8
    # $length = $result.length
    # $end = $length - $start
    # $newstring = ($result).substring($start,$end)
    # $newstring = $newstring.trim()
    # $newstring -imatch "Bytes : .*? " 1>$null

    # [int64]($Matches[0].split(':')[-1].trim())/1gb
    # }else {
        # return $null
    # }
    # method 2:
    Try{
        If( (Get-Item $path -ea 1).psiscontainer ){
            $objFSO = New-Object -ComObject Scripting.FileSystemObject
            $io_size= $objFSO.GetFolder($path).Size
        }else{
            $io_size=(gi (resolve-path $path)).Length
        }
    }Catch{
        return -1
    }
    return ("{0:N2} MB" -f ($io_size/"1$size_unit"))
}

Function Show-DeskTop(){
    (New-Object -ComObject shell.application).toggleDesktop()
}
Function Lock-WorkStation {
    $signature = @"
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool LockWorkStation();
"@
    $name = "Win32LockWorkStation"
    $LockWorkStation = Add-Type -memberDefinition $signature -name $name -namespace Win32Functions -passthru
    $LockWorkStation::LockWorkStation() | Out-Null
}
Function Get-MapDrive(){
    Get-WmiObject win32_logicaldisk -Filter 'drivetype=4'|select DeviceID,ProviderName, @{n="Size(G)";e={"{0:N2}" -f ($_.Size/1gb)}}, @{n="Free(G)";e={"{0:N2}" -f ($_.FreeSpace/1gb)}}
}
Function New-AutosysLogFolder(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$VMServer,
        [string]$folder
    )
    $autosys_log_path="\\$VMServer\C$\Program Files\CA\Workload Automation AE\SystemAgent\WA1_AGENT\log\$folder"  # initilize autosys log path
    $autosys_out_log="$autosys_log_path\out.log"                                                                  # normal log file path and 
    $autosys_err_log="$autosys_log_path\err.log"                                                                  # error log file path
    $autosys_out_log,$autosys_err_log|%{New-IOItem $_}  
}
Function Get-SID(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$domain=(Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0].ToUpper(),
        [string]$fid=$env:username 
    )
    Try{
        $User = New-Object System.Security.Principal.NTAccount($domain, $fid)
        $sid = $User.Translate([System.Security.Principal.SecurityIdentifier]).value
        return $sid
    }Catch{
        return $null
    }Finally{}
}
Function Stop-Powershell(){Get-Process powershell* -ErrorAction Ignore|?{$_.id -ne $pid}|kill -force}
Function Stop-IE(){ Get-Process iexplore -ErrorAction Ignore|kill -force}
Function Stop-Mstsc(){ Get-Process mstsc -ErrorAction Ignore|kill -force}
